﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

using Buddy.BehaviorTree;
using Buddy.Common;
using Buddy.Common.Math;
using Buddy.CommonBot;
using Buddy.Swtor;
using Buddy.Swtor.Objects;

using Action = Buddy.BehaviorTree.Action;

namespace WingIt.Routines.Mirrors
{
    [RevisionControlId("$Id: Mirror.cs 686 2012-08-12 16:12:20Z Chinajade $")]
    public class Mirror
    {
        public delegate TorAbility  TorAbilityDelegate(object context);

        /// <summary>
        /// 
        /// </summary>
        ///  8Aug2012-05:00UTC chinajade
        public static void Initialize(CharacterClass myNormalizedClass, CharacterClass myNormalizedMirrorClass)
        {
            MirrorAbilitiesMap_Forward = MirrorData.MasterMirror_AbilityMap[myNormalizedClass];
            MirrorAbilitiesMap_Reverse = MirrorData.MasterMirror_AbilityMap[myNormalizedMirrorClass];

            MirrorBuffsMap_Forward = MirrorData.MasterMirror_BuffMap[myNormalizedClass];
            MirrorBuffsMap_Reverse = MirrorData.MasterMirror_BuffMap[myNormalizedMirrorClass];

            MirrorDebuffMap_Forward = MirrorData.MasterMirror_DebuffMap[myNormalizedClass];
            MirrorDebuffMap_Reverse = MirrorData.MasterMirror_DebuffMap[myNormalizedMirrorClass];

            MirrorData.ValidateMaintenance();
        }
        private static Dictionary<string, string> MirrorAbilitiesMap_Forward = null;
        private static Dictionary<string, string> MirrorAbilitiesMap_Reverse = null;
        private static Dictionary<string, string> MirrorBuffsMap_Forward = null;
        private static Dictionary<string, string> MirrorBuffsMap_Reverse = null;
        private static Dictionary<string, string> MirrorDebuffMap_Forward = null;
        private static Dictionary<string, string> MirrorDebuffMap_Reverse = null;

        #region Cast-Spell methods

        /// <summary Cast>
        /// <para>Convenience wrapper around Mirror.Cast(string abilityName, CharacterSelectionDelegate onCharacter, BooleanValueDelegate requirements)</para>
        /// <para>where, onCharacter is provided as <c>TargetSelect.ForAttack()</c></para>
        /// </summary>
        /// <param name="abilityName"></param>
        /// <param name="requirements"></param>
        /// <returns>Composite for use in a behavior tree PrioritySelector</returns>
        /// 21Jun2012-20:47UTC chinajade
        public static Composite Cast(string abilityName, BooleanValueDelegate requirements = null)
        {
            return Mirror.Cast(abilityName, on => TargetSelect.ForAttack(), requirements);
        }

        /// <summary Cast>
        /// <para>Casts ABILITYNAME on ONCHARACTER, if REQUIREMENTS are met.  ABILITYNAME can be supplied as the toon's class ability, or the toon's mirror-class ability--
        /// this method will figure out the correct one to use.</para>
        /// <para>For use in a behavior tree <c>PrioritySelector</c>.
        /// Returns <c>RunStatus.Success</c> if the ABILITYNAME was actually cast.  Otherwise, returns <c>RunStatus.Failure</c></para>
        /// <para>Failure to cast the spell can occur for a number of reasons:<list type="bullet">
        /// <item><description><para> * the REQUIREMENTS are not met</para></description></item> 
        /// <item><description><para> * the toon has not learned the ability (or the ABILITYNAME is misspelled by the caller)</para></description></item> 
        /// <item><description><para> * the target is not within the spell's range</para></description></item> 
        /// <item><description><para> * the ability is on cooldown</para></description></item>
        /// </list></para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If ABILITYNAME does not belong to the toon's class, or the toon's mirror-class, a MaintenanceError is logged.</para></description></item>
        /// <item><description><para> * If REQUIREMENTS are omitted, ability use will be blindly attempted.</para></description></item> 
        /// <item><description><para> * The caller does not need to provide REQUIREMENTS for ability learned, distance to use, or cooldowns.
        /// All these checks are implicitly built into this method.</para></description></item> 
        /// </list>
        /// <item><description><para>Prefer this method when possible since this it does class-mirror substitutions.</para></description></item>
        /// </para>
        /// </summary>
        /// <param name="abilityName"></param>
        /// <param name="requirements"></param>
        /// <returns>Composite for use in a behavior tree PrioritySelector</retur, "ns>
        /// 21Jun2012-20:47UTC chinajade
        public static Composite Cast(string abilityName, CharacterSelectionDelegate onCharacter, BooleanValueDelegate requirements = null)
        {
            Logger.ContractRequires(() => !string.IsNullOrEmpty(abilityName), () => "!string.IsNullOrEmpty(abilityName)");
            // Since onCharacter is a delegate, we must enforce any contract at BT-traversal time, not BT-build time

            requirements = requirements ?? (ctx => true);   // Resolve a null argument into something sane

            string myMethodName = Logger.GetMyMethodName(true);

            //Logger.Write("Instant " + IsInstaCast(abilityName) + "IsMoving " + BuddyTor.Me.IsMoving);

            // CombatRoutine.Cast() checks that the ability is not on cooldown, and
            // that the target is in range, so no need to duplicate that here.
            // (Verified by a conversation with Aevitas while he was looking at the BWcore source -- 28Jun2012-19:07UTC chinajade)
            return new Sequence(
                Mirror.CreateBTSeq_SanityCheckForCast(myMethodName, abilityName),
                // AbilityManager.CanCast() returns 'true' if we're in motion, so need additional qualification for that here (IsInstaCast() || !Me.IsMoving)...
                WingIt.Instance.Cast(LookupAbilityName(abilityName), onCharacter, castWhen => (onCharacter != null) && requirements(castWhen) && (IsInstaCast(abilityName) || !BuddyTor.Me.IsMoving)),
                Mirror.CreateBTSeq_TrackAbilityUseAndWaitForComplete(abilityName)
                );
        }

        /// <summary CastOnGround>
        /// <para>Casts ABILITYNAME on LOCATION, if REQUIREMENTS are met.  ABILITYNAME can be supplied as the toon's class ability, or the toon's mirror-class ability--
        /// this method will figure out the correct one to use..</para>
        /// <para>For use in a behavior tree <c>PrioritySelector</c>.</para>
        /// <para>Returns <c>RunStatus.Success</c> if the ABILITYNAME was actually cast.  Otherwise, returns <c>RunStatus.Failure</c></para>
        /// <para>Failure to cast the spell can occur for a number of reasons:<list type="bullet">
        /// <item><description><para> * the REQUIREMENTS are not met</para></description></item> 
        /// <item><description><para> * the toon has not learned the ability (or the ABILITYNAME is misspelled by the caller)</para></description></item> 
        /// <item><description><para> * the target location is not within the spell's range</para></description></item> 
        /// <item><description><para> * the ability is on cooldown</para></description></item> 
        /// </list></para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If ABILITYNAME does not belong to the toon's class, or the toon's mirror-class, a MaintenanceError is logged.</para></description></item>
        /// <item><description><para> * If REQUIREMENTS are omitted, ability use will be blindly attempted.</para></description></item> 
        /// <item><description><para> * The caller does not need to provide REQUIREMENTS for ability learned, distance to use, or cooldowns.
        /// All these checks are implicitly built into this method.</para></description></item> 
        /// </list>
        /// <item><description><para>Prefer this method when possible since this it does class-mirror substitutions.</para></description></item>
        /// </para>
        /// </summary>
        /// <param name="abilityName"></param>
        /// <param name="requirements"></param>
        /// <returns>Composite for use in a behavior tree PrioritySelector</returns>
        /// 21Jun2012-20:47UTC chinajade
        public static Composite CastOnGround(string abilityName, CommonBehaviors.Retrieval<Buddy.Common.Math.Vector3> location, BooleanValueDelegate requirements)
        {
            Logger.ContractRequires(() => !string.IsNullOrEmpty(abilityName), () => "!string.IsNullOrEmpty(abilityName)");
            // Since location is a delegate (Retrieval), we must enforce any contract at BT-traversal time, not BT-build time.

            requirements = requirements ?? (ctx => true);   // Resolve a null argument into something sane

            string myMethodName = Logger.GetMyMethodName();

            return (new Decorator(castOnGroundWhen =>
            {
                if (!requirements(castOnGroundWhen))
                { return false; }

                TorAbility mirrorAbility = BuddyTor.Me.GetMirrorAbility(abilityName);
                if ((mirrorAbility == null) || CooldownTracker.IsOnCooldown(mirrorAbility))
                { return false; }

                Vector3 position = (location == null) ? Vector3.Zero : location(castOnGroundWhen);
                if (position == Vector3.Zero)
                { return false; }

                float distance = Vector3.Distance(BuddyTor.Me.Position, position);
                if ((distance < mirrorAbility.MinRange) && (distance > mirrorAbility.MaxRange))
                { return false; }

                // TODO: We'd should check for Clearcasting buff, and is this ability affected by it.
                return ((mirrorAbility.ActionPointCost > 0) && (BuddyTor.Me.ActionPoints > mirrorAbility.ActionPointCost))
                        || ((mirrorAbility.EnergyCost > 0) && (BuddyTor.Me.Energy > mirrorAbility.EnergyCost))
                        || ((mirrorAbility.ForceCost > 0) && (BuddyTor.Me.Force > mirrorAbility.ForceCost))
                        || ((mirrorAbility.HeatCost > 0) && ((BuddyTor.Me.ResourceStat + mirrorAbility.HeatCost) < 100));
            },

                new Sequence(
                    Mirror.CreateBTSeq_SanityCheckForCast(myMethodName, abilityName),
                    new Action(castOnGroundContext =>
                    {
                        TorAbility mirrorAbility = BuddyTor.Me.GetMirrorAbility(abilityName);

                        // TODO: We should force a "Face", but there is no API that allows facing a position--it must be TorObject
                        EffectResult effectResult = EffectResult.Unknown;
                        BuddyTor.Me.AbilityActivate(mirrorAbility, location(castOnGroundContext), out effectResult);

                        bool didCast = (effectResult == EffectResult.Ok);
                        if (!didCast)
                        {
                            Logger.Write("{0} failed to cast '{1}' (EffectResult: {2})",
                                myMethodName, mirrorAbility.Name, effectResult.ToString());
                            return RunStatus.Failure;
                        }

                        Logging.Write("Casting " + mirrorAbility.Name);
                        return RunStatus.Success;
                    }),
                    Mirror.CreateBTSeq_TrackAbilityUseAndWaitForComplete(abilityName)
                    )
                ));
        }

        /// <summary BuffSelf>
        /// <para>Convenience method to cast BUFFNAME on <c>BuddyTor.Me</c>, but only if the buff does not currently exist on the toon, and REQUIREMENTS are met.</para>
        /// <para>For use in a behavior tree <c>PrioritySelector</c>.</para>
        /// <para>Returns <c>RunStatus.Success</c> if the BUFFNAME was actually cast.  Otherwise, returns <c>RunStatus.Failure</c></para>
        /// <para>Notes:
        /// <item><description><para> * if REQUIREMENTS are omitted, buffing will be blindly attempted if BUFFNAME is missing.</para></description></item> 
        /// <item><description><para> * the caller does not need to provide REQUIREMENTS for ability learned, distance to use, or cooldowns.
        /// All these checks are implicitly built into this method.</para></description></item> 
        /// <item><description><para> * the name of the buff received, must also be the same as the name of the spell used to cast the buff, BUFFNAME.</para>
        /// <para>For instance, casting "Uber" must yield a buff called "Uber", not "Uber (Force)" or "Uber (Tech)".</para>
        /// <para>If such a disparity exists, you must use a normal Cast() with the appropriate requirements, instead.</para></description></item> 
        /// </para>
        /// </summary>
        /// <param name="buffName"></param>
        /// <param name="requirements"></param>
        /// <returns>Composite for use in a behavior tree PrioritySelector</returns>
        /// 21Jun2012-20:47UTC chinajade
        public static Composite BuffSelf(string buffName, BooleanValueDelegate requirements = null)
        {
            Logger.ContractRequires(() => !string.IsNullOrEmpty(buffName), () => "!string.IsNullOrEmpty(buffName)");

            requirements = requirements ?? (ctx => true);   // Resolve a null argument into something sane

            string mirrorBuffName = LookupAbilityName(buffName);

            return (Mirror.Cast(mirrorBuffName, buffSelfOn => BuddyTor.Me, buffSelfWhen => !BuddyTor.Me.HasBuff(mirrorBuffName) && requirements(buffSelfWhen)));
        }

        #endregion

        // NB: HasMirrorBuff() and HasMirrorDebuff() methods are in MirrorExtensions.cs.

        #region Lookup methods

        /// <summary Lookup>
        /// 
        /// </summary>
        /// <param name="masterMirrorMap"></param>
        /// <param name="srcName"></param>
        /// <param name="pairTypeName"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        private static string Lookup(Dictionary<string, string> mirrorMap_Forward, Dictionary<string, string> mirrorMap_Reverse, string srcName, string pairTypeName)
        {
            Logger.ContractRequires(() => mirrorMap_Forward != null, () => "mirrorMap_Forward != null");
            Logger.ContractRequires(() => mirrorMap_Reverse != null, () => "mirrorMap_Reverse != null");
            Logger.ContractRequires(() => !string.IsNullOrEmpty(srcName), () => "!string.IsNullOrEmpty(srcName)");
            Logger.ContractRequires(() => !string.IsNullOrEmpty(pairTypeName), () => "!string.IsNullOrEmpty(pairTypeName)");

            string destName;

            if (mirrorMap_Forward.TryGetValue(srcName, out destName))
                { return (srcName); }

            if (mirrorMap_Reverse.TryGetValue(srcName, out destName))
                { return (destName); }

            Logger.MaintenanceError("{0} '{2}' is neither a {3} nor a {4} {1}.\n    Spelling error in {0} name?\n    Attempting use with wrong class?",
                                    pairTypeName, pairTypeName.ToLower(),
                                    srcName, WingIt.MyNormalizedClass, WingIt.MyNormalizedMirrorClass);
            return (string.Empty);
        }

        /// <summary LookupAbility>
        /// <para>Given MIRRORABILITYNAME, returns the correct ability name for the class.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * MIRRORABILITYNAME may be the toon's class ability name, or the mirror-class ability name.</para></description></item>
        /// <item><description><para> * If the ability name cannot be located, a MaintenanceError is logged, and <c>String.Empty</c> is returned.
        /// This is usually due to spelling errors by the caller.</para></description></item>
        /// <item><description><para> * Prefer using a <c>Mirror.Cast()</c> method variant rather looking up a abiilty name explicitly with this method.</para></description></item>
        /// </list>
        /// </para>
        /// </summary>
        /// <param name="mirrorAbilityName"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        public static string LookupAbilityName(string mirrorAbilityName)
        {
            return Lookup(MirrorAbilitiesMap_Forward, MirrorAbilitiesMap_Reverse, mirrorAbilityName, "Ability");
        }

        /// <summary LookupBuff>
        /// <para>Given MIRRORBUFFNAME, returns the correct buff name for the class.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * MIRRORBUFFNAME may be the toon's class buff name, or the mirror-class buff name.</para></description></item>
        /// <item><description><para> * If the buff name cannot be located, a MaintenanceError is logged, and <c>String.Empty</c> is returned.
        /// This is usually due to spelling errors by the caller.</para></description></item>
        /// <item><description><para> * Prefer using the <c>TorCharacter.HasMirrorBuff()</c> extension method rather looking up a buff name explicitly with this method.</para></description></item>
        /// </list>
        /// </para>
        /// </summary>
        /// <param name="mirrorBuffName"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        public static string LookupBuffName(string mirrorBuffName)
        {
            return Lookup(MirrorBuffsMap_Forward, MirrorBuffsMap_Reverse, mirrorBuffName, "Buff");
        }

        /// <summary LookupDebuff>
        /// <para>Given MIRRORDEBUFFNAME, returns the correct debuff name for the class.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * MIRRORDEBUFFNAME may be the toon's class debuff name, or the mirror-class debuff name.</para></description></item>
        /// <item><description><para> * If the debuff name cannot be located, a MaintenanceError is logged, and <c>String.Empty</c> is returned.
        /// This is usually due to spelling errors by the caller.</para></description></item>
        /// <item><description><para> * Prefer using the <c>TorCharacter.HasMirrorDebuff()</c> extension method rather looking up a debuff name explicitly with this method.</para></description></item>
        /// </list>
        /// </para>
        /// </summary>
        /// <param name="mirrorDebuffName"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        public static string LookupDebuffName(string mirrorDebuffName)
        {
            return Lookup(MirrorDebuffMap_Forward, MirrorDebuffMap_Reverse, mirrorDebuffName, "Debuff");
        }
        
        #endregion  // Lookup methods

        #region Utility

        #region Classes

        /// <summary GetMirrorClass>
        /// <para>Given CHARACTERCLASS, this method returns the 'mirror' class.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * CHARACTERCLASS may be either a basic class or an advanced class.
        /// The appropriate mirror class is returned in either case.</para></description></item>
        /// <item><description><para> * The mirror class of CharacterClass.Unknown is CharacterClass.Unknown.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <param name="characterClass"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        public static CharacterClass GetMirrorClass(CharacterClass characterClass)
        {
            CharacterClass mirrorClass;

            if (MirrorData.Class_EmpireToRepublic.TryGetValue(characterClass, out mirrorClass))
            { return mirrorClass; }

            if (MirrorData.Class_RepublicToEmpire.TryGetValue(characterClass, out mirrorClass))
            { return mirrorClass; }

            Logger.MaintenanceError("Character class '{0}' is neither Empire nor Republic", characterClass);
            return (CharacterClass.Unknown);
        }

        /// <summary IsEmpireClass>
        /// <para>Returns true, if the CHARACTERCLASS is associated with the Empire.</para>
        /// </summary>
        /// <param name="characterClass"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        public static bool IsEmpireClass(CharacterClass characterClass)
        {
            CharacterClass dummy;

            return MirrorData.Class_EmpireToRepublic.TryGetValue(characterClass, out dummy);
        }

        /// <summary IsRepublicClass>
        /// Returns true, if the CHARACTERCLASS is associated with the Republic.
        /// </summary>
        /// <param name="characterClass"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        public static bool IsRepublicClass(CharacterClass characterClass)
        {
            CharacterClass dummy;

            return MirrorData.Class_RepublicToEmpire.TryGetValue(characterClass, out dummy);
        }
        #endregion

        #region Check Ability

        /// <summary CooldownTracker>
        /// <para>The CooldownTracker keeps up with when abilities are last used, and whether enough time has elapsed for them to
        /// be used again. The tracker makes its decisions based upon the maximum of <c>CooldownTime</c> or <c>GlobalCooldownTime</c>.
        /// For spammable abilities, the latter is the limiting factor.</para>
        /// <para>To use this class:<list type="bullet">
        /// <item><description><para> * Call <c>CooldownTracker.IsOnCooldown</c> any time you need to find out if ability is on cooldown</para></description></item>
        /// <item><description><para> * Call <c>CooldownTracker.AbilityUsed</c> any time you use an ability.
        /// This keeps the tracker up to date.</para></description></item>
        /// </list></para>
        /// </summary>
        // NB: The BW API has similar functionality built in for use with TorCaracters.  You find this functionality
        // in TorPlayer.IsAbilityReady() and AbilityManager.CanCast().  However, there is no corresponding functionality
        // for abilities that are used 'on the ground' instead of TorCharacters.  Thus, the need for this class.
        public static class CooldownTracker
        {
            /// <summary>
            /// Call this method to determine if an ability is on Cooldown.  Both the ability's particular cooldown
            /// and the global cooldown are taking into account.  For spammable abilities, the latter is the limiting
            /// factor.
            /// </summary>
            /// <param name="ability"></param>
            /// <returns></returns>
            public static bool IsOnCooldown(TorAbility ability)
            {
                DateTime lastUsed;

                if (cooldowns.TryGetValue(ability, out lastUsed))
                {
                    TimeSpan maxCooldown = TimeSpan.FromSeconds(Math.Max(ability.CooldownTime, ability.GlobalCooldownTime));
                    return ((DateTime.Now - lastUsed) <= maxCooldown);
                }

                return (false); // Ability hasn't been used yet
            }

            /// <summary>
            /// Call this method any time you use an ability to keep the CooldownTracker up-to-date.
            /// </summary>
            /// <param name="ability"></param>
            public static void AbilityUsed(TorAbility ability)
            {
                cooldowns[ability] = DateTime.Now + cheatFactorDelay;
            }

            private static Dictionary<TorAbility, DateTime> cooldowns = new Dictionary<TorAbility, DateTime>();
            private static TimeSpan cheatFactorDelay = TimeSpan.FromMilliseconds(100);
        }

        /// <summary>
        /// Return true if the Ability has no cast/channel time
        /// </summary>
        /// <param name="abilityName"></param>
        /// <returns></returns>
        public static bool IsInstaCast(string abilityName)
        {
            Logger.ContractRequires(() => !string.IsNullOrEmpty(abilityName), () => "!string.IsNullOrEmpty(abilityName)");

            TorAbility ability = BuddyTor.Me.GetMirrorAbility(abilityName);

            // Must consider that ability may not be known yet...
            return ((ability == null) ? false : ability.IsInstaCast());
        }

        private static Composite CreateBTSeq_TrackAbilityUseAndWaitForComplete(string abilityName)
        {
            return new Sequence(
                mirrorAbility => BuddyTor.Me.GetMirrorAbility(abilityName),
                new Action(mirrorAbility =>
                {
                    if ((TorAbility)mirrorAbility == null)
                    {
                        Logger.MaintenanceError("{0}: Attempting to track an unknown ability, '{1}'",
                                                Logger.GetMyMethodName(), abilityName);
                        return (RunStatus.Failure);
                    }
                    CooldownTracker.AbilityUsed((TorAbility)mirrorAbility);
                    return (RunStatus.Success);
                }),
                Mirror.WaitForCastingStart(mirrorAbility => (TorAbility)mirrorAbility),
                Mirror.WaitForCastingComplete(mirrorAbility => (TorAbility)mirrorAbility)
                );
        }

        private static Composite CreateBTSeq_SanityCheckForCast(string myMethodName, string abilityName)
        {
            return new DecoratorContinue(whenAbilityNameKnown => string.IsNullOrEmpty(LookupAbilityName(abilityName)),
                new Action(abilityNameUnknown =>
                {
                    Logger.MaintenanceError("{0}: Unable to locate mirror ability for '{1}'", myMethodName, abilityName);
                    return (RunStatus.Failure);
                })
                );
        }

        #endregion

        #region Waiting-for-Cast

        /// <summary>
        /// <para>Waits for an ABILITY to start casting.  Once casting starts, the wait is truncated.</para>
        /// <para>Such a wait is frequently necessary because BuddyWing tree-traversal runs faster than the SWtOR client.
        /// Without such a wait, casting a spell, then an immediate check for IsCasting will fail,
        /// because the SWtOR client doesn't have a chance to react.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * No wait is conducted, if the ABILITY is insta-cast.</para></description></item>
        /// <item><description><para> * This behavior was built for use in a BehaviorTree.Sequence container.
        /// Attempts to use it in a Selector-based container will not yield the desired results.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <param name="abilityDelegate"></param>
        /// <returns><para>a behavior tree Composite for use in a Sequence.</para>
        /// <para>The Composite returns RunStatus.Running to the BT parent if the wait is in progress.
        /// Otherwise, returns RunStatus.Success indicating any required waiting is complete.</para>
        /// </returns>
        /// 10Jul2012-15:58UTC chinajade
        public static Composite WaitForCastingStart(TorAbilityDelegate abilityDelegate)
        {
            // Allow the SWtOR client/server time to update state...
            return new WaitContinue(TimeSpan.FromMilliseconds(3 * 1000),
                                     abortWaitingWhen => abilityDelegate(abortWaitingWhen).IsInstaCast() || BuddyTor.Me.IsCasting,
                                     new Action());
        }

        /// <summary>
        /// <para>This behavior waits for casting of ABILITY to cease.  The behavior will also truncate the wait if STOPWAITINGWHEN returns true.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If STOPWAITINGWHEN is omitted, a value of false is used.</para></description></item>
        /// <item><description><para> * No wait is conducted, if the ABILITY is insta-cast.</para></description></item>
        /// <item><description><para> * The wait is truncated if casting stops for external reasons--for instance,
        /// a mob knocks the toon out of a channeled spell.</para></description></item>
        /// <item><description><para> * This behavior was built for use in a BehaviorTree.Sequence container.
        /// Attempts to use it in a Selector-based container will not yield the desired results.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <param name="ability"></param>
        /// <returns><para>a behavior tree Composite for use in a Sequence.</para>
        /// <para>The Composite returns RunStatus.Running to the BT parent if the wait is in progress.
        /// Otherwise, returns RunStatus.Success indicating any required waiting is complete.</para>
        /// </returns>
        /// 10Jul2012-15:58UTC chinajade
        public static Composite WaitForCastingComplete(TorAbilityDelegate abilityDelegate, CanRunDecoratorDelegate stopWaitingWhen = null)
        {
            stopWaitingWhen = stopWaitingWhen ?? (ctx => false);

            // Wait for ability cast to actually complete...
            return new WaitContinue(TimeSpan.FromMilliseconds(60 * 1000),
                                     abortWaitingWhen => abilityDelegate(abortWaitingWhen).IsInstaCast() || !BuddyTor.Me.IsCasting || stopWaitingWhen(abortWaitingWhen),
                                     new Action());
        }
        #endregion

        #endregion  // Utility methods

        #region Validation
        /// <summary>
        /// Flags any maintenance errors that may be introduced into the master mapping tables.
        /// </summary>
        /// <param name="abilityPairs"></param>
        /// <param name="allowedEmpireClasses"></param>
        /// <param name="allowedRepublicClasses"></param>
        /// <returns></returns>
        /// 21Jun2012-20:47UTC chinajade
        public static bool ValidateTableMaintenance(MirrorData.AbilityPair[]   abilityPairs,
                                                         string                     varName_abilityPairs,
                                                         Dictionary<CharacterClass, Dictionary<string, string>>  masterMirrorMap,
                                                         string                     varName_masterMirrorMap,
                                                         CharacterClass[]           allowedEmpireClasses,
                                                         CharacterClass[]           allowedRepublicClasses)
        {
            Logger.ContractRequires(() => abilityPairs != null, () => "abilityPairs != null");
            Logger.ContractRequires(() => !string.IsNullOrEmpty(varName_abilityPairs), () => "!string.IsNullOrEmpty(varName_abilityPairs)");
            Logger.ContractRequires(() => masterMirrorMap != null, () => "masterMirrorMap != null");
            Logger.ContractRequires(() => !string.IsNullOrEmpty(varName_masterMirrorMap), () => "!string.IsNullOrEmpty(varName_masterMirrorMap)");
            Logger.ContractRequires(() => allowedEmpireClasses != null, () => "allowedEmpireClasses != null");
            Logger.ContractRequires(() => allowedEmpireClasses.Length > 0, () => "allowedEmpireClasses.Length > 0");
            Logger.ContractRequires(() => allowedRepublicClasses != null, () => "allowedRepublicClasses != null");
            Logger.ContractRequires(() => allowedRepublicClasses.Length > 0, () => "allowedRepublicClasses.Length > 0");

            IEnumerable<CharacterClass>     allowedClasses  = allowedEmpireClasses.Concat(allowedRepublicClasses);
            Dictionary<string, string>      dummy;
            bool                            isValid         = true;

            // Validate allowed classes --
            foreach (var characterClass in allowedEmpireClasses)
            {
                if (!IsEmpireClass(characterClass))
                {
                    Logger.MaintenanceError("Argument 'allowedEmpireClasses' has class '{0}' that is not Empire faction",
                                            characterClass);
                    isValid = false;
                }
            }

            foreach (var characterClass in allowedRepublicClasses)
            {
                if (!IsRepublicClass(characterClass))
                {
                    Logger.MaintenanceError("Argument 'allowedRepublicClasses' has class '{0}' that is not Republic faction",
                                            characterClass);
                    isValid = false;
                }
            }

            // Make certain forward and reverse maps are present --
            foreach (var characterClass in allowedClasses)
            {
                if (!masterMirrorMap.TryGetValue(characterClass, out dummy))
                {
                    Logger.MaintenanceError("MirrorAbilityMap '{0}' is missing an entry for the '{1}' key.",
                                            varName_abilityPairs, characterClass);
                    isValid = false;
                }
            }

            // Examine each pair entry --
            foreach (var entry in abilityPairs)
            {
                // Empire ability name should not be empty...
                if (string.IsNullOrEmpty(entry.Empire.AbilityName))
                {
                    Logger.MaintenanceError("In {0} for entry {1}...\n"
                                             + "    Empire ability name may not be empty.",
                                             varName_masterMirrorMap, entry.ToString());
                    isValid = false;
                }

                // Empire class needs to be one of those allowed...
                if ((allowedEmpireClasses != null) && !allowedEmpireClasses.Contains(entry.Empire.NormalizedClass))
                {
                    Logger.MaintenanceError("In {0} for entry {1}...\n"
                                             + "    Class '{2}' is not allowed for Empire Class in the table.\n"
                                             + "    Allowed classes: {3}.",
                                             varName_masterMirrorMap, entry.ToString(), entry.Empire.NormalizedClass,
                                             string.Join(", ", allowedEmpireClasses));
                    isValid = false;
                }

                // Republic ability name should not be empty...
                if (string.IsNullOrEmpty(entry.Republic.AbilityName))
                {
                    Logger.MaintenanceError("In {0} for entry {1}...\n"
                                             + "    Republic ability name may not be empty.",
                                             varName_masterMirrorMap, entry.ToString());
                    isValid = false;
                }

                // Republic class needs to be one of those allowed...
                if ((allowedRepublicClasses != null) && !allowedRepublicClasses.Contains(entry.Republic.NormalizedClass))
                {
                    Logger.MaintenanceError("In {0} for entry {1}...\n"
                                             + "    Class '{2}' is not allowed for Republic Class in the table.\n"
                                             + "    Allowed classes: {3}.",
                                             varName_masterMirrorMap, entry.ToString(), entry.Republic.NormalizedClass,
                                             string.Join(", ", allowedRepublicClasses));
                    isValid = false;
                }

                // Republic & Empire classes should be mirrors of each other...
                if (GetMirrorClass(entry.Empire.NormalizedClass) != entry.Republic.NormalizedClass)
                {
                    Logger.MaintenanceError("In {0} for entry {1}...\n"
                                             + "    Class '{2}' is the mirror of class '{3}' (not the provided '{4}').",
                                             varName_masterMirrorMap, entry.ToString(),
                                             GetMirrorClass(entry.Empire.NormalizedClass), entry.Empire.NormalizedClass,
                                             entry.Republic.NormalizedClass);
                    isValid = false;
                }
            }

            return (isValid);
        }
        #endregion  // Validation
    }
}
